using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;
using OrchardCore.DynamicCache;
using OrchardCore.Environment.Cache;
using OrchardCore.Modules;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Threading.Tasks;

namespace Ruitu.Edu.TrainingDemo.Services;

/// <summary>
/// 日期时间缓存服务实现 - 演示多种缓存策略的使用
/// 
/// 主要用途：
/// - 实现内存缓存和动态缓存的不同策略
/// - 演示缓存失效机制
/// - 展示路由相关的缓存变化
/// 
/// 使用场景：
/// - 需要高性能的本地缓存
/// - 需要分布式缓存支持
/// - 需要基于路由的缓存隔离
/// 
/// 设计原因：
/// - 利用 ASP.NET Core 内置的 IMemoryCache
/// - 使用 Orchard Core 的动态缓存服务
/// - 支持缓存标签和信号机制
/// 
/// 设计模式：
/// - 依赖注入模式：通过构造函数注入所需服务
/// - 策略模式：提供多种缓存实现
/// - 工厂模式：通过服务容器创建缓存实例
/// </summary>
[SuppressMessage(
        "Usage",
        "VSTHRD003:Avoid awaiting foreign Tasks",
        Justification = "We need LocalNowAsync to create cachedDate.")]
public class DateTimeCachingService : IDateTimeCachingService
{
    public const string MemoryCacheKey = "Ruitu.Edu.TrainingDemo.MemoryCache.DateTime";
    public const string DynamicCacheKey = "Ruitu.Edu.TrainingDemo.DynamicCache.DateTime";
    public const string DynamicCacheTag = "Ruitu.Edu.TrainingDemo.DynamicCache.DateTime.Tag";

    // 您已经看到了用于获取当前 UTC 日期的 IClock 服务。此服务可用于根据站点设置获取当前本地日期。
    // 日期也可以从 UTC 转换或转换为 UTC。
    private readonly ILocalClock _localClock;

    // IMemoryCache 服务是 ASP.NET Core 中的内置服务。如果您想要一个本地到当前进程的快速缓存，请使用此服务。
    // 请注意，如果您的应用程序在多个服务器上运行，此缓存不会在节点之间共享。
    // 要了解有关 IMemoryCache 的更多信息，请访问 https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory
    private readonly IMemoryCache _memoryCache;

    // 动态缓存主要用于缓存形状。它基于内置的 ASP.NET Core IDistributedCache 服务，
    // 默认情况下由 DistributedMemoryCache 实现。如果您只想缓存简单值，就像使用 IMemoryCache 一样，
    // 但以在多个服务器上运行应用程序时也在服务器之间共享缓存条目的方式，则直接使用 IDistributedCache。
    // 要了解有关分布式缓存和 IDistributedCache 的更多信息，请访问 https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed
    private readonly IDynamicCacheService _dynamicCacheService;

    // 我们使用 ISignals 能够向内存缓存发送信号以使给定条目失效。
    private readonly ISignal _signal;

    // 标签缓存是用于标记缓存数据并通过其标签使缓存失效的服务。
    private readonly ITagCache _tagCache;

    /// <summary>
    /// 初始化日期时间缓存服务的新实例
    /// </summary>
    /// <param name="memoryCache">内存缓存服务</param>
    /// <param name="localClock">本地时钟服务</param>
    /// <param name="dynamicCacheService">动态缓存服务</param>
    /// <param name="tagCache">标签缓存服务</param>
    /// <param name="signal">信号服务</param>
    public DateTimeCachingService(
        IMemoryCache memoryCache,
        ILocalClock localClock,
        IDynamicCacheService dynamicCacheService,
        ITagCache tagCache,
        ISignal signal)
    {
        _memoryCache = memoryCache;
        _localClock = localClock;
        _dynamicCacheService = dynamicCacheService;
        _tagCache = tagCache;
        _signal = signal;
    }

    /// <summary>
    /// 使用 IMemoryCache 获取或创建缓存的 DateTime 对象
    /// </summary>
    /// <returns>当前本地日期时间或缓存的值</returns>
    public async Task<DateTime> GetMemoryCachedDateTimeAsync()
    {
        if (!_memoryCache.TryGetValue(MemoryCacheKey, out DateTime cachedDate))
        {
            cachedDate = (await _localClock.LocalNowAsync).DateTime;

            _memoryCache.Set(MemoryCacheKey, cachedDate, GetMemoryCacheChangeToken());
        }

        return cachedDate;
    }

    /// <summary>
    /// 使用 Orchard Core 动态缓存以 30 秒过期时间缓存 DateTime 对象
    /// </summary>
    /// <returns>当前本地日期时间或缓存的值</returns>
    public Task<DateTime> GetDynamicCachedDateTimeWith30SecondsExpiryAsync() =>
        // 要缓存对象或检索它们，必须使用 CacheContext 对象。它有一个强制性属性，即缓存键。
        // 缓存参数也在此对象中设置。要了解其工作原理，请进入此方法。
#pragma warning disable SA1114 // Parameter list should follow declaration (necessary for the comment)
        GetOrCreateDynamicCachedDateTimeAsync(
            // 注意 CacheContext 对象有可链接的方法，因此您可以使用它们来填充设置。
            new CacheContext(DynamicCacheKey).WithExpiryAfter(TimeSpan.FromSeconds(30)));

#pragma warning restore SA1114 // Parameter list should follow declaration

    /// <summary>
    /// 缓存当前日期，类似于前一个，但不设置 30 秒过期时间，而是标记它以便稍后失效。
    /// 此外，此缓存将按路由区分。这意味着在一个路由上缓存的 DateTime 在其他路由上将无法访问，
    /// 因此将为该特定路由缓存另一个 DateTime。Orchard Core 中已经实现了多种不同的区分器，
    /// 请参阅：https://docs.orchardcore.net/en/latest/docs/reference/modules/DynamicCache/
    /// </summary>
    /// <returns>当前本地日期时间或缓存的值</returns>
    public Task<DateTime> GetDynamicCachedDateTimeVariedByRoutesAsync() =>
        GetOrCreateDynamicCachedDateTimeAsync(
            new CacheContext(DynamicCacheKey)
                .AddContext("route")
                .AddTag(DynamicCacheTag));

    /// <summary>
    /// 使内存缓存和所有已标记的动态缓存失效
    /// </summary>
    public async Task InvalidateCachedDateTimeAsync()
    {
        // 如前所述，ISignal 服务用于使内存缓存失效。如果有多个与令牌相关的缓存条目，这将使所有缓存条目失效。
        await _signal.SignalTokenAsync(MemoryCacheKey);

        // ITagCache.RemoveTagAsync 将使所有使用给定标签标记的动态缓存失效。
        await _tagCache.RemoveTagAsync(DynamicCacheTag);
    }

    /// <summary>
    /// 基于缓存键使用 ISignal 服务生成此更改令牌。它用于使内存缓存失效。
    /// 您不仅可以将其用作使特定条目失效的另一种方式，还可以同时使多个条目失效：
    /// 您也可以将多个缓存条目绑定到同一个信号。
    /// </summary>
    /// <returns>内存缓存更改令牌</returns>
    private IChangeToken GetMemoryCacheChangeToken() => _signal.GetToken(MemoryCacheKey);

    /// <summary>
    /// 获取或创建动态缓存的日期时间
    /// </summary>
    /// <param name="cacheContext">缓存上下文</param>
    /// <returns>缓存的日期时间</returns>
    private async Task<DateTime> GetOrCreateDynamicCachedDateTimeAsync(CacheContext cacheContext)
    {
        // 现在我们有了缓存上下文，我们尝试获取对象。对象始终需要是字符串。
        var cachedDateTimeText = await _dynamicCacheService.GetCachedValueAsync(cacheContext);

        // 如果日期时间文本不为 null，则解析为 DateTime，否则使用 ILocalClock 服务将其设置为当前日期。
        var cachedDateTime = cachedDateTimeText != null ?
            DateTime.Parse(cachedDateTimeText, CultureInfo.InvariantCulture) :
            (await _localClock.LocalNowAsync).DateTime;

        // 如果日期时间文本为 null（意味着它没有被缓存），则缓存 DateTime 对象（在这种情况下是当前日期）。
        if (cachedDateTimeText == null)
        {
            await _dynamicCacheService.SetCachedValueAsync(
                cacheContext,
                cachedDateTime.ToString(CultureInfo.InvariantCulture));
        }

        return cachedDateTime;
    }

    // 下一站：Views/Cache/Index.cshtml
}
